﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TfsBuildLauncher
{
    public interface IArg
    {
        string Name { get; }
        bool IsRequired { get; }
        bool HasValue { get; }
        void SetValue(string value);
    }

    public class Arg<T> : IArg
    {
        public string Name { get; private set; }
        public bool IsRequired { get; private set; }
        public bool IsList { get; private set; }
        private T value;
        private List<T> values;
        private bool initialized;


        public Arg(string name, bool isRequired, bool isList)
        {
            this.Name = name;
            this.IsList = isList;
            this.IsRequired = isRequired;
        }

        private static T Parse(string stringValue)
        {
            TypeConverter tc = TypeDescriptor.GetConverter(typeof(T));
            T value = (T)tc.ConvertFromString(null, CultureInfo.InvariantCulture, stringValue);
            return value;
        }

        public void SetValue(string value)
        {
            if (IsList)
            {
                if (this.values == null)
                {
                    this.values = new List<T>();
                }
                this.values.Add(Arg<T>.Parse(value));
            }
            else
            {
                this.value = Arg<T>.Parse(value);
            }
            initialized = true;
        }

        public bool HasValue
        {
            get
            {
                if (IsList)
                {
                    return this.values != null;
                }
                else
                {
                    return !EqualityComparer<T>.Default.Equals(this.value, default(T));
                }
            }
        }

        public T Value
        {
            get
            {
                if (!this.initialized && this.IsRequired)
                {
                    throw new InvalidOperationException("Argument " + this.Name + " has not been initialized. Use IsValid or Parse the command line first.");
                }
                return this.value;
            }
        }

        public List<T> Values
        {
            get
            {
                if (!this.initialized && this.IsRequired)
                {
                    throw new InvalidOperationException("Argument " + this.Name + " has not been initialized. Use IsValid or Parse the command line first.");
                }
                return this.values;
            }
        }
    }

    public class ArgumentsParser
    {
        private List<String> commandLineArguments;
        private List<IArg> arguments;
        private bool? isValid;
        public string ErrorMessage { get; private set; }

        public bool IsValid
        {
            get
            {
                if (!isValid.HasValue)
                {
                    Parse();
                }
                return isValid.Value;
            }
            private set
            {
                isValid = value;
            }
        }

        public ArgumentsParser(string[] args)
        {
            this.commandLineArguments = new List<String>(args);
            this.arguments = new List<IArg>();
        }

        public Arg<T> Declare<T>(string name, bool IsRequired = false, bool IsList = false)
        {
            Arg<T> arg = new Arg<T>(name, IsRequired, IsList);
            arguments.Add(arg);
            return arg;
        }

        public void Parse()
        {
            foreach (string commandLineArg in this.commandLineArguments)
            {
                bool valid = false;
                foreach (IArg argument in this.arguments)
                {
                    if (commandLineArg.StartsWith(String.Concat("/", argument.Name, ":")))
                    {
                        valid = true;
                        string stringValue = commandLineArg.Substring(argument.Name.Length + 2);
                        argument.SetValue(stringValue);
                        break;
                    }
                }
                if (!valid)
                {
                    this.IsValid = false;
                    this.ErrorMessage = "Invalid argument: " + commandLineArg;
                    return;
                }
            }
            foreach (IArg argument in this.arguments)
            {
                if (argument.IsRequired && !argument.HasValue)
                {
                    this.isValid = false;
                    this.ErrorMessage = "Missing argument value for " + argument.Name;
                    return;
                }
            }
            IsValid = true;
        }
    }
}
